﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MiniCPU.Events;

namespace MiniCPU.Entities
{
    /// <summary>
    /// Limited Data Class.
    /// </summary>
    public class LimitedData : Data
    {
        #region Constants
        /// <summary>
        /// Zeros to add as header to fill number of bits.
        /// </summary>
        public const string Zeros = "00000000000000000000000000000000";
        #endregion

        #region Variables
        /// <summary>
        /// Start Bit.
        /// </summary>
        private int startBit;

        /// <summary>
        /// End Bit.
        /// </summary>
        private int endBit;

        /// <summary>
        /// Indicates whether the limited data is partial.
        /// </summary>
        private bool isPartial;

        /// <summary>
        /// Source register in case.
        /// </summary>
        private LimitedData source;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="LimitedData"/> class.
        /// </summary>
        /// <param name="name">The name of the limited data.</param>
        /// <param name="value">The value of the limited data.</param>
        /// <param name="numberOfBits">The number of bits.</param>
        public LimitedData(string name, Int32 value, int numberOfBits)
        {
            this.isPartial = false;
            this.Name = name;
            this.NumberOfBits = numberOfBits;
            this.UpdateValue(value);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LimitedData"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="source">The source.</param>
        /// <param name="startBit">The start bit.</param>
        /// <param name="endBit">The end bit.</param>
        public LimitedData(string name, LimitedData source, int startBit, int endBit) :
            this(name, source.GetPartOfData(startBit, endBit).IntValue, startBit - endBit + 1)
        {
            this.isPartial = true;
            this.startBit = startBit;
            this.endBit = endBit;
            this.source = source;
            this.source.DataChanged += new DataChangedEventHandler(source_DataChanged);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LimitedData"/> class.
        /// </summary>
        /// <param name="name">The name of the limited data.</param>
        /// <param name="value">The value of the limited data.</param>
        /// <param name="valueBase">The value base.</param>
        /// <param name="numberOfBits">The number of bits.</param>
        public LimitedData(string name, string value, int valueBase, int numberOfBits) :
            this(name, Convert.ToInt32(value, valueBase), numberOfBits)
        {
            this.Name = name;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LimitedData"/> class.
        /// </summary>
        /// <param name="name">The name of the limited data.</param>
        /// <param name="value">The value of the limited data.</param>
        /// <param name="numberOfBits">The number of bits.</param>
        public LimitedData(string name, byte value, int numberOfBits) :
            this(name, Convert.ToInt32(value), numberOfBits)
        {
            this.Name = name;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LimitedData"/> class.
        /// </summary>
        /// <param name="name">The name of the limited data.</param>
        /// <param name="value">if set to <c>true</c> [value].</param>
        public LimitedData(string name, bool value)
            : this(name, Convert.ToInt32(value), 1)
        {
            this.Name = name;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LimitedData"/> class.
        /// </summary>
        /// <param name="name">The name of the limited data.</param>
        /// <param name="value">The value of the limited data.</param>
        /// <param name="numberOfBits">The number of bits.</param>
        public LimitedData(string name, byte[] value, int numberOfBits) :
            this(name, GetIntFromByteArray(value), numberOfBits)
        {
        }
        #endregion

        #region Events
        /// <summary>
        /// Occurs when [data changed].
        /// </summary>
        public event DataChangedEventHandler DataChanged;
        #endregion

        #region Properties
        /// <summary>
        /// Gets or setsThe name of the limited data.
        /// </summary>
        /// <value>The name of the limited data.</value>
        public string Name { get; set; }

        /// <summary>
        /// Gets the signed int value.
        /// </summary>
        /// <value>The signed int value.</value>
        public Int32 SignedIntValue
        {
            get
            {
                if (IntValue > Math.Pow(2, this.NumberOfBits - 1) - 1)
                {
                    return this.IntValue - (int)Math.Pow(2, this.NumberOfBits);
                }
                else
                {
                    return this.IntValue;
                }
            }
        }

        /// <summary>
        /// Gets or sets the boolean value in case this is a flag.
        /// </summary>
        /// <value><c>true</c> if the boolean value; otherwise, <c>false</c>.</value>
        public bool BooleanValue
        {
            get
            {
                if (this.NumberOfBits == 1)
                {
                    return Convert.ToBoolean(this.IntValue);
                }
                else
                {
                    throw new InvalidCastException();
                }
            }

            set
            {
                if (this.NumberOfBits == 1)
                {
                    this.UpdateValue(Convert.ToInt32(value));
                }
                else
                {
                    throw new InvalidCastException();
                }
            }
        }

        /// <summary>
        /// Gets or sets the number of bits.
        /// </summary>
        /// <value>The number of bits.</value>
        public int NumberOfBits { get; protected set; }

        /// <summary>
        /// Gets or sets a value indicating whether [data changed].
        /// </summary>
        /// <value><c>true</c> if [data changed]; otherwise, <c>false</c>.</value>
        protected bool IsDataChanged { get; set; }
        #endregion

        #region Methods

        /// <summary>
        /// Duplicates this instance.
        /// </summary>
        /// <returns>A duplicate of this instance.</returns>
        public LimitedData Duplicate()
        {
            return new LimitedData(this.Name + ":Duplicate", this.IntValue, this.NumberOfBits);
        }

        /// <summary>
        /// Gets a reference to the part of data.
        /// </summary>
        /// <param name="startBit">The start bit.</param>
        /// <param name="endBit">The end bit.</param>
        /// <returns>The specified part of the data.</returns>
        public LimitedData GetPartOfDataReferenced(int startBit, int endBit)
        {
            int start = this.NumberOfBits - startBit - 1;
            int count = startBit - endBit + 1;
            string partialString = this.BinaryStringValue.Substring(start, count);

            return new LimitedData(
                this.Name + "(" + startBit.ToString() + ".." + endBit.ToString(),
                this,
                startBit,
                endBit);
        }
        
        /// <summary>
        /// Gets a duplicate of the part of data.
        /// </summary>
        /// <param name="startBit">The start bit.</param>
        /// <param name="endBit">The end bit.</param>
        /// <returns>The specified part of the data.</returns>
        public LimitedData GetPartOfData(int startBit, int endBit)
        {
            int start = this.NumberOfBits - startBit - 1;
            int count = startBit - endBit + 1;
            string partialString = this.BinaryStringValue.Substring(start, count);

            return new LimitedData(
                this.Name + "(" + startBit.ToString() + ".." + endBit.ToString(),
                partialString,
                2,
                count);
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return this.HexStringValue + " - " + this.SignedIntValue;
        }

        private void source_DataChanged(object sender, DataChangedEventArgs e)
        {
            this.UpdateValue(this.source.GetPartOfData(startBit, endBit).IntValue);
        }

        /// <summary>
        /// Gets the string filled with zeros.
        /// </summary>
        /// <param name="stringBase">The string base.</param>
        /// <returns>string filled with zeros.</returns>
        protected override string GetString(int stringBase)
        {
            string tmp = Convert.ToString(this.IntValue, stringBase);
            if (tmp.Length < this.NumberOfBits / (int)Math.Log(stringBase, 2))
            {
                return Zeros.Substring(0, (this.NumberOfBits / (int)Math.Log(stringBase, 2)) - tmp.Length) + tmp;
            }
            else
            {
                return tmp;
            }
        }

        /// <summary>
        /// Executes the event handlers.
        /// </summary>
        protected void ExecuteEventHandlers()
        {
            if (this.DataChanged != null)
            {
                this.DataChanged(
                    this, 
                    new DataChangedEventArgs()
                    {
                        NewValue = new LimitedData(this.Name + ":DataChangedEventArgs", this.IntValue, this.NumberOfBits)
                    });
            }
        }

        /// <summary>
        /// Updates the value of the limited data.
        /// </summary>
        /// <param name="value">The value of the limited data.</param>
        protected override void UpdateValue(int value)
        {
            if (value >= 0)
            {
                if (Convert.ToString(value, 2).Length > this.NumberOfBits)
                {
                    throw new ArgumentOutOfRangeException();
                }
            }
            else
            {
                if (value < -1 * Math.Pow(2, this.NumberOfBits - 1))
                {
                    throw new ArgumentOutOfRangeException();
                }
            }

            if (value != this.IntValue)
            {
                this.IsDataChanged = true;
            }
            else
            {
                this.IsDataChanged = false;
            }

            base.UpdateValue(value);
        }
        #endregion
    }
}
